package com.luolc.emojirain;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Image;
import ohos.agp.components.StackLayout;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.media.image.PixelMap;

import com.chinasoft_ohos.commontools.toast.Toast;
import com.chinasoft_ohos.commontools.util.AttrValue;
import com.luolc.emojirain.rxohos.OhosSchedulers;
import com.luolc.emojirain.utils.Pools;
import com.luolc.emojirain.utils.Randoms;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import rx.Observable;
import rx.Subscription;
import rx.subscriptions.CompositeSubscription;
import timber.log.Timber;

/**
 * Emoji rain animates in this layout.
 *
 * @author LuoLiangchen
 * @since 2016/12/13
 */

public class EmojiRainLayout extends StackLayout {
    private static final float RELATIVE_DROP_DURATION_OFFSET = 0.25F;
    private static final int DEFAULT_PER = 6;
    private static final int DEFAULT_DURATION = 8000;
    private static final int DEFAULT_DROP_DURATION = 2400;
    private static final int DEFAULT_DROP_FREQUENCY = 500;
    private CompositeSubscription mSubscriptions;
    private int mWindowHeight;
    private int mEmojiPer;
    private int mDuration;
    private int mDropAverageDuration;
    private int mDropFrequency;
    private int emoji_standard_size;
    private List<Element> mEmojis;
    private Pools.SynchronizedPool<Image> mEmojiPool;

    {
        emoji_standard_size = dip2px(36);
    }

    /**
     * 构造函数
     *
     * @param context 上下文
     */
    public EmojiRainLayout(Context context) {
        this(context, null);
    }

    /**
     * 构造函数
     *
     * @param context 上下文
     * @param attrs 自定义属性
     */
    public EmojiRainLayout(Context context, AttrSet attrs) {
        this(context, attrs, null);
    }

    /**
     * 构造函数
     *
     * @param context 上下文
     * @param attrs 自定义属性
     * @param defStyleAttr 自定义风格
     */
    public EmojiRainLayout(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    /**
     * 设置下落的表情数量
     *
     * @param per 表情数量
     */
    public void setPer(int per) {
        mEmojiPer = per;
    }

    /**
     * 设置下落总时长
     *
     * @param duration 下落时长
     */
    public void setDuration(int duration) {
        mDuration = duration;
    }

    /**
     * 设置下落时间
     *
     * @param dropDuration 下落时间
     */
    public void setDropDuration(int dropDuration) {
        mDropAverageDuration = dropDuration;
    }

    /**
     * 设置频率
     *
     * @param frequency 频率
     */
    public void setDropFrequency(int frequency) {
        mDropFrequency = frequency;
    }

    /**
     * 添加表情
     *
     * @param emoji PixelMap
     */
    public void addEmoji(PixelMap emoji) {
        mEmojis.add(new PixelMapElement(emoji));
    }

    /**
     * 添加表情
     *
     * @param emoji 资源Element
     */
    public void addEmoji(Element emoji) {
        mEmojis.add(emoji);
    }

    /**
     * 添加表情
     *
     * @param resId 资源id
     */
    public void addEmoji(int resId) {
        PixelMapElement pixelMapElement = null;
        try {
            pixelMapElement = new PixelMapElement(getResourceManager().getResource(resId));
        } catch (IOException e) {
            Timber.i(e.getMessage());
        } catch (NotExistException e) {
            Timber.i(e.getMessage());
        }
        mEmojis.add(pixelMapElement);
    }

    /**
     * 清空表情池
     */
    public void clearEmojis() {
        mEmojis.clear();
    }

    /**
     * Stop dropping animation after all emojis in the screen currently
     * dropping out of the screen.
     */
    public void stopDropping() {
        mSubscriptions.clear();
    }

    /**
     * Start dropping animation.
     * The animation will last for n flow(s), which n is {@code mDuration}
     * divided by {@code mDropFrequency}.
     * The interval between two flows is {@code mDropFrequency}.
     * There will be {@code mEmojiPer} emojis dropping in each flow.
     * The dropping animation for a specific emoji is a random value with mean
     * {@code mDropAverageDuration} and relative offset {@code RELATIVE_DROP_DURATION_OFFSET}.
     */
    public void startDropping() {
        initEmojisPool();
        Randoms.setSeed(7);
        Randoms.setRanges(getWidth());
        mWindowHeight = getWindowHeight();
        Subscription subscription = Observable.interval(mDropFrequency, TimeUnit.MILLISECONDS)
            .take(mDuration / mDropFrequency)
            .flatMap(flow -> Observable.range(0, mEmojiPer))
            .map(image -> mEmojiPool.acquire())
            .filter(ep -> ep != null)
            .observeOn(OhosSchedulers.mainThread())
            .subscribe(this::startDropAnimationForSingleEmoji, Throwable::printStackTrace);
        mSubscriptions.add(subscription);
    }

    private void init(Context context, AttrSet attrs) {
        mSubscriptions = new CompositeSubscription();
        mEmojis = new ArrayList<>();
        if (attrs != null) {
            mEmojiPer = AttrValue.get(attrs, "per", DEFAULT_PER);
            mDuration = AttrValue.get(attrs, "duration", DEFAULT_DURATION);
            mDropAverageDuration = AttrValue.get(attrs, "dropDuration", DEFAULT_DROP_DURATION);
            mDropFrequency = AttrValue.get(attrs, "dropFrequency", DEFAULT_DROP_FREQUENCY);
        }
    }

    private void startDropAnimationForSingleEmoji(final Image emoji) {
        AnimatorProperty animatorProperty = emoji.createAnimatorProperty();
        animatorProperty.setDuration((int)
            (mDropAverageDuration * Randoms.floatAround(1, RELATIVE_DROP_DURATION_OFFSET)));
        float startX = Randoms.getStartX();
        float endX = Randoms.getStopX();
        animatorProperty.moveFromX(startX).moveToX(endX).moveFromY(-180).moveToY(mWindowHeight);
        animatorProperty.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
        animatorProperty.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
            }

            @Override
            public void onStop(Animator animator) {
                mEmojiPool.release(emoji);
            }

            @Override
            public void onCancel(Animator animator) {
            }

            @Override
            public void onEnd(Animator animator) {
            }

            @Override
            public void onPause(Animator animator) {
            }

            @Override
            public void onResume(Animator animator) {
            }
        });
        animatorProperty.start();
    }

    private int getWindowHeight() {
        return getHeight();
    }

    private void initEmojisPool() {
        final int emojiTypeCount = mEmojis.size();
        if (emojiTypeCount == 0) {
            throw new IllegalStateException("There are no emojis");
        }

        clearDirtyEmojisInPool();
        final int expectedMaxEmojiCountInScreen =
            (int) ((1 + RELATIVE_DROP_DURATION_OFFSET)
                * mEmojiPer
                * mDropAverageDuration
                / ((float) mDropFrequency));
        if (expectedMaxEmojiCountInScreen <= 0) {
            Toast.show(getContext().getString(ResourceTable.String_toast_reenter));
            return;
        }
        mEmojiPool = new Pools.SynchronizedPool<>(expectedMaxEmojiCountInScreen);
        for (int i = 0; i < expectedMaxEmojiCountInScreen; i++) {
            final Image emoji = generateEmoji(mEmojis.get(i % emojiTypeCount));
            addComponent(emoji);
            mEmojiPool.release(emoji);
        }
    }

    private Image generateEmoji(Element emojiDrawable) {
        Image emoji = new Image(getContext());
        emoji.setImageElement(emojiDrawable);
        emoji.setScaleMode(Image.ScaleMode.CENTER);
        double positive = Randoms.positiveGaussian() * 0.6;
        final int width = (int) (emoji_standard_size * (1.0 + positive));
        final int height = (int) (emoji_standard_size * (1.0 + positive));
        final LayoutConfig params = new LayoutConfig(width, height);
        params.setMarginTop(-height);
        params.setMarginLeft((int) (-0.5F * width));
        emoji.setLayoutConfig(params);
        Timber.i("huanxue generateEmoji width:" + width + "  height:" + height);
        return emoji;
    }

    private void clearDirtyEmojisInPool() {
        if (mEmojiPool != null) {
            Image dirtyEmoji;
            while ((dirtyEmoji = mEmojiPool.acquire()) != null) {
                removeComponent(dirtyEmoji);
            }
        }
    }

    private int dip2px(float dp) {
        return AttrHelper.vp2px(dp, getContext());
    }
}
